/*
 * IM - Image I/O Library
 * Copyright (c) 1996 CENPES/Petrobras, TeCGraf/PUC-Rio.
 * Antonio E. Scuri - scuri@tecgraf.puc-rio.br
 * 
 * IMJPG.C
 * 
 * Contains the implementation of the JPG file format functions.
 *
 * History:
 *          1996.04.05 - Changed the structure of the module to the new format.
 *          1996.06.14 - Finished inicial version.
 *
 */

#include "im.h"
#include "imio.h"
#include "imjpg.h"
#include "binfile.h"

#include <stdlib.h>
#include <memory.h>
#include <setjmp.h>
#include <stdio.h>

#include "jpeglib.h"


#include "jinclude.h"
#include "jpeglib.h"
#include "jerror.h"

/* outros arquivos relacionados com libjpeg editados para a IM */
/*   jinclude.h   */
/*   jconfig.h    */
/*   jdatadst.c   */

/* libjpeg error handlers */

typedef struct _imJPGerror_mgr 
{
  struct jpeg_error_mgr pub;	/* "public" fields */
  jmp_buf setjmp_buffer;	/* for return to caller */
} imJPGerror_mgr;


METHODDEF(void)
imJPGerror_exit (j_common_ptr cinfo)
{
  /* cinfo->err really points to a my_error_mgr struct, so coerce pointer */
  imJPGerror_mgr* err_mgr = (imJPGerror_mgr*)cinfo->err;

  /* Return control to the setjmp point */
  longjmp(err_mgr->setjmp_buffer, 1);
}


METHODDEF(void)
imJPGoutput_message (j_common_ptr cinfo)
{
  (void)cinfo;
}

METHODDEF(void)
imJPGemit_message (j_common_ptr cinfo, int msg_level)
{
  (void)cinfo; (void)msg_level;
}



typedef struct _imFileJPG
{
  unsigned long line_size;    /* line buffer size */
  unsigned char* line_buffer; /* line buffer */

  struct jpeg_decompress_struct dinfo;
  struct jpeg_compress_struct cinfo;
  imJPGerror_mgr jerr;

  binFile* file_bin;

  unsigned char gray_map[256];
} imFileJPG;


static imFile* imJPGReadOpen(char* filename, int *err)
{
  imFile *ifile = (imFile*)malloc(sizeof(imFile));
  imFileJPG *file_jpg = (imFileJPG*)malloc(sizeof(imFileJPG));
  binFile* file_bin;

  file_bin = binFileOpen(filename, 0, 0);
	if (file_bin == NULL)
  {
    free(file_jpg);
    free(ifile);
    if (err) *err = IM_ERR_OPEN;
    return NULL;
  }

  file_jpg->file_bin = file_bin;
  ifile->file = file_jpg;
  file_jpg->line_buffer = NULL;

  file_jpg->dinfo.err = jpeg_std_error(&file_jpg->jerr.pub);
  file_jpg->jerr.pub.error_exit = imJPGerror_exit;
  file_jpg->jerr.pub.output_message = imJPGoutput_message;
  file_jpg->jerr.pub.emit_message = imJPGemit_message;

  /* Establish the setjmp return context for error_exit to use. */
  if (setjmp(file_jpg->jerr.setjmp_buffer)) 
  {
    /* If we get here, the JPEG code has signaled an error.
     * We need to clean up the JPEG object, close the input file, and return. */
    jpeg_destroy_decompress(&file_jpg->dinfo);
    binFileClose(file_bin);
    if (file_jpg->line_buffer) free(file_jpg->line_buffer);
    free(file_jpg);
    free(ifile);
    if (err) *err = IM_ERR_FORMAT;
    return NULL;
  }

  /* Now we can initialize the JPEG decompression object. */
  jpeg_create_decompress(&file_jpg->dinfo);

  /* Step 2: specify data source (eg, a file) */
  jpeg_stdio_src(&file_jpg->dinfo, (FILE*)file_bin);

  /* Step 3: read file parameters with jpeg_read_header() */
  if (jpeg_read_header(&file_jpg->dinfo, TRUE) != JPEG_HEADER_OK)
  {
    jpeg_destroy_decompress(&file_jpg->dinfo);
    binFileClose(file_bin);
    free(file_jpg);
    free(ifile);
    if (err) *err = IM_ERR_FORMAT;
    return NULL;
  }

	ifile->type = IM_RGB;
  ifile->pal_size = 0;
  ifile->width = file_jpg->dinfo.image_width;
  ifile->height = file_jpg->dinfo.image_height;
  ifile->compress = IM_DEFAULT;

  if (imResolutionCB)
  {
    double xres = 0, yres = 0;
    int res_unit = IM_RES_NONE;

    if (file_jpg->dinfo.density_unit != 0)
    {
      if (file_jpg->dinfo.density_unit == 1)
        res_unit = IM_RES_DPI;
      else
        res_unit = IM_RES_DPC;

      xres = file_jpg->dinfo.X_density;
      yres = file_jpg->dinfo.Y_density;
    }

    imResolutionCB(filename, &xres, &yres, &res_unit);
  }
  
  if (file_jpg->dinfo.jpeg_color_space == JCS_GRAYSCALE)
  {
	  ifile->type = IM_MAP;
    ifile->pal_size = 256;
  }
  else if (file_jpg->dinfo.jpeg_color_space == JCS_UNKNOWN)
  {
    if (err) *err = IM_ERR_TYPE;
    return ifile;
  }

  if (ifile->type == IM_MAP)
    file_jpg->dinfo.out_color_space = JCS_GRAYSCALE;
  else
  {
    if (file_jpg->dinfo.jpeg_color_space == JCS_YCbCr) 
      file_jpg->dinfo.out_color_space = JCS_RGB;
    else if (file_jpg->dinfo.jpeg_color_space == JCS_YCCK) 
      file_jpg->dinfo.out_color_space = JCS_CMYK;
  }

  /* Step 5: Start decompressor */
  if (jpeg_start_decompress(&file_jpg->dinfo) == FALSE)
  {
    jpeg_destroy_decompress(&file_jpg->dinfo);
    binFileClose(file_bin);
    free(file_jpg);
    free(ifile);
    if (err) *err = IM_ERR_READ;
    return NULL;
  }

  /* now initialize the imFile control data */

  ifile->split = 0;     /* image is RGB packed */
  ifile->planes[0] = 0; /* sets the default plane order since split = 0 */
  ifile->planes[1] = 1;
  ifile->planes[2] = 2;
  ifile->cur_line = ifile->height - 1;  /* first line */
  ifile->pre_colors = 1;  /* color map is before the image */

  file_jpg->line_size = file_jpg->dinfo.output_width * file_jpg->dinfo.output_components;
  file_jpg->line_buffer = (unsigned char*)malloc(file_jpg->line_size);

  if (err) *err = IM_ERR_NONE;

  return ifile;
}


static imFile* imJPGWriteOpen(char* filename, int width, int height, int type, int compress, int pal_size, int *err)
{
  imFile *ifile = (imFile*)malloc(sizeof(imFile));
  imFileJPG *file_jpg = (imFileJPG*)malloc(sizeof(imFileJPG));
  binFile* file_bin;

  /* can not save with no compression */
  if (compress == IM_NONE)
  {
    if (err) *err = IM_ERR_COMP;
    return NULL;
  }

  file_bin = binFileOpen(filename, 1, 0);
	if (file_bin == NULL)
  {
    free(ifile);
    if (err) *err = IM_ERR_OPEN;
    return NULL;
  }

  file_jpg->file_bin = file_bin;
  ifile->file = file_jpg;

  file_jpg->cinfo.err = jpeg_std_error(&file_jpg->jerr.pub);
  file_jpg->jerr.pub.error_exit = imJPGerror_exit;
  file_jpg->jerr.pub.output_message = imJPGoutput_message;
  file_jpg->jerr.pub.emit_message = imJPGemit_message;
  
  /* Establish the setjmp return context for error_exit to use. */
  if (setjmp(file_jpg->jerr.setjmp_buffer)) 
  {
    /* If we get here, the JPEG code has signaled an error.
     * We need to clean up the JPEG object, close the input file, and return. */
    jpeg_destroy_compress(&file_jpg->cinfo);
    binFileClose(file_bin);
    free(file_jpg);
    free(ifile);
    if (err) *err = IM_ERR_WRITE;
    return NULL;
  }
  
  jpeg_create_compress(&file_jpg->cinfo);

  /* Step 2: specify data destination (eg, a file) */
  jpeg_stdio_dest(&file_jpg->cinfo, (FILE*)file_bin);

  /* Step 3: set parameters for compression */
  file_jpg->cinfo.image_width = width; 	/* image width and height, in pixels */
  file_jpg->cinfo.image_height = height;

  if (type == IM_MAP)
  {
    file_jpg->cinfo.input_components = 1;		/* # of color components per pixel */
    file_jpg->cinfo.in_color_space = JCS_GRAYSCALE; 	/* colorspace of input image */
  }
  else
  {
    file_jpg->cinfo.input_components = 3;		/* # of color components per pixel */
    file_jpg->cinfo.in_color_space = JCS_RGB; 	/* colorspace of input image */
  }

  jpeg_set_defaults(&file_jpg->cinfo);

  jpeg_set_quality(&file_jpg->cinfo, 100, TRUE /* limit to baseline-JPEG values */);

  if (imResolutionCB)
  {
    double xres = 0, yres = 0;
    int res_unit = IM_RES_NONE;

    imResolutionCB(filename, &xres, &yres, &res_unit);

    if (res_unit != IM_RES_NONE)
    {
      if (res_unit == IM_RES_DPI)
        file_jpg->cinfo.density_unit = 1;
      else
        file_jpg->cinfo.density_unit = 2;

      file_jpg->cinfo.X_density = (unsigned short)xres;
      file_jpg->cinfo.Y_density = (unsigned short)yres;
    }
  }

  /* Step 4: Start compressor */
  jpeg_start_compress(&file_jpg->cinfo, TRUE);

  ifile->width = width;
  ifile->height = height;
  ifile->type = type;
  ifile->pal_size = (type == IM_RGB)? 0: pal_size;
  ifile->compress = compress;

  /* now initialize the imFile control data */

  ifile->split = 0;     /* image is RGB packed */
  ifile->planes[0] = 0; /* sets the default plane order since split = 0 */
  ifile->planes[1] = 1;
  ifile->planes[2] = 2;
  ifile->cur_line = ifile->height - 1;  /* first line */
  ifile->pre_colors = 1;  /* color map is before the image */

  file_jpg->line_size = ifile->width * file_jpg->cinfo.input_components;
  file_jpg->line_buffer = (unsigned char*)malloc(file_jpg->line_size);

  if (err) *err = IM_ERR_NONE;

  return ifile;
}


static void imJPGClose(imFile* ifile)
{
  imFileJPG *file_jpg = (imFileJPG*)ifile->file;
  binFile* file_bin = file_jpg->file_bin; 

  if (setjmp(file_jpg->jerr.setjmp_buffer)) 
  {
    if (file_bin->WriteFlag)
      jpeg_destroy_compress(&file_jpg->cinfo);
    else
      jpeg_destroy_decompress(&file_jpg->dinfo);

    binFileClose(file_bin);
    if (file_jpg->line_buffer) free(file_jpg->line_buffer);
    free(file_jpg);
    free(ifile);
    return;
  }

  if (file_bin->WriteFlag)
  {
    if (ifile->cur_line != ifile->height - 1) jpeg_finish_compress(&file_jpg->cinfo);
    jpeg_destroy_compress(&file_jpg->cinfo);
  }
  else
  {
    if (ifile->cur_line != ifile->height - 1) jpeg_finish_decompress(&file_jpg->dinfo);
    jpeg_destroy_decompress(&file_jpg->dinfo);
  }

  binFileClose(file_bin);
  if (file_jpg->line_buffer) free(file_jpg->line_buffer);
  free(file_jpg);
  free(ifile);
}


static int imJPGReadColors(imFile* ifile, long *colors)
{
  int c;
  (void)ifile;

  for (c = 0; c < 256; c++)
    colors[c] = imEncodeColor((unsigned char)c,(unsigned char)c,(unsigned char)c);

  return IM_ERR_NONE;
}


static int imJPGWriteColors(imFile* ifile, long *colors)
{
  int j;
  imFileJPG *file_jpg = (imFileJPG*)ifile->file;
  unsigned char r, g, b;

  for(j = 0; j < ifile->pal_size; j++)
  {
    imDecodeColor(&r,&g,&b,colors[j]);
    file_jpg->gray_map[j] = (unsigned char)((r * 30 + g * 59 + b * 11) / 100);
  }

  return IM_ERR_NONE;
}


static int imJPGReadLine(imFile* ifile, unsigned char *map)
{
  imFileJPG *file_jpg = (imFileJPG*)ifile->file;

  if (setjmp(file_jpg->jerr.setjmp_buffer)) 
    return IM_ERR_READ;

  if (jpeg_read_scanlines(&file_jpg->dinfo, &file_jpg->line_buffer, 1) == 0)
    return IM_ERR_READ;

	memcpy(map, file_jpg->line_buffer, ifile->width);

  /* we have just read one more line */
  ifile->cur_line--;

  return IM_ERR_NONE;
}


static int imJPGWriteLine(imFile* ifile, unsigned char *map)
{
  int j;
  imFileJPG *file_jpg = (imFileJPG*)ifile->file;
  unsigned char* line_buffer;

  line_buffer = file_jpg->line_buffer;
    
  for (j = 0; j < ifile->width; j++) 
    line_buffer[j] = file_jpg->gray_map[map[j]];

  if (setjmp(file_jpg->jerr.setjmp_buffer)) 
    return IM_ERR_WRITE;

  if (jpeg_write_scanlines(&file_jpg->cinfo, &file_jpg->line_buffer, 1) == 0)
    return IM_ERR_WRITE;

  /* we have just write one more line */
  ifile->cur_line--;

  return IM_ERR_NONE;
}


static int imJPGReadLineRGB(imFile* ifile, unsigned char *red, unsigned char *green, unsigned char *blue)
{
  int d, j;
  imFileJPG *file_jpg = (imFileJPG*)ifile->file;
  unsigned char* line_buffer;
  int Cyan, Magenta, Yellow, Black, Aux;

  if (setjmp(file_jpg->jerr.setjmp_buffer)) 
    return IM_ERR_READ;

  if (jpeg_read_scanlines(&file_jpg->dinfo, &file_jpg->line_buffer, 1) == 0)
    return IM_ERR_READ;

  line_buffer = file_jpg->line_buffer;
    
  switch (file_jpg->dinfo.out_color_space)
  {
  case JCS_RGB:
    for (j = 0; j < ifile->width; j++)
		{
      d = j * 3;
      red[j] = line_buffer[d];
			green[j] = line_buffer[d + 1];
			blue[j] = line_buffer[d + 2];
		}
    break;
  case JCS_CMYK:
    for (j = 0; j < ifile->width; j++)
		{
      d = j * 4;
      
      if (file_jpg->dinfo.jpeg_color_space == JCS_YCCK) 
      {
        /*se o arquivo foi gerado pelo adobe, a codificacao e invertida*/
        Cyan    = 255 - line_buffer[d];
        Magenta = 255 - line_buffer[d+1];
        Yellow  = 255 - line_buffer[d+2];
        Black   = 255 - line_buffer[d+3];
      }
      else
      {
        Cyan    = line_buffer[d];
        Magenta = line_buffer[d+1];
        Yellow  = line_buffer[d+2];
        Black   = line_buffer[d+3];
      }

      Aux = 255 - Black;
     
      red[j]   = (unsigned char)((Aux * (255 - Cyan)) / 255);
			green[j] = (unsigned char)((Aux * (255 - Magenta)) / 255);
			blue[j]  = (unsigned char)((Aux * (255 - Yellow)) / 255);
		}
    break;
  default:
    return IM_ERR_TYPE;
  }

  /* we have just read one more line */
  ifile->cur_line--;

  return IM_ERR_NONE;
}


static int imJPGWriteLineRGB(imFile* ifile, unsigned char *red, unsigned char *green, unsigned char *blue)
{
  int d, j;
  imFileJPG *file_jpg = (imFileJPG*)ifile->file;
  unsigned char* line_buffer;

  line_buffer = file_jpg->line_buffer;

  for (j = 0; j < ifile->width; j++)
  {
    d=j*3;
    line_buffer[d] = red[j];
    line_buffer[d+1] = green[j];
    line_buffer[d+2] = blue[j];
  }

  if (setjmp(file_jpg->jerr.setjmp_buffer)) 
    return IM_ERR_WRITE;

  if (jpeg_write_scanlines(&file_jpg->cinfo, &file_jpg->line_buffer, 1) == 0)
    return IM_ERR_WRITE;

  /* we have just write one more line */
  ifile->cur_line--;

  return IM_ERR_NONE;
}


static int imJPGRegisterCallback(imCallback cb, int id)
{
  (void)cb;
  (void)id;
  /* callback not found */
  return 0;
}


static imFormat _imFormatJPG =
{
  0,
  imJPGRegisterCallback,
  imJPGReadOpen,
  imJPGWriteOpen,
  imJPGClose,
  imJPGReadColors,
  imJPGWriteColors,
  imJPGReadLine,
  imJPGWriteLine,
  imJPGReadLineRGB,
  imJPGWriteLineRGB
};

imFormat* imFormatJPG(void)
{
  return &_imFormatJPG;
}



